package fast.calc.monk;

import fast.calc.AttackType;
import fast.calc.AttacksBlock;
import fast.calc.Buffs;
import fast.calc.Fast;
import fast.calc.Stats;
import fast.calc.WeaponStats;
import fast.data.BaseStats;
import fast.data.Spec;
import fast.data.Stat;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class FastMonk extends Fast {

	private AttacksMonk attacks;
	
	private Map<String, Double> dps = new HashMap<String, Double>();
	private Map<String, Double> dpsOnlyCrit = new HashMap<String, Double>();
	
	private double mh_swings_per_sec;
	private double oh_swings_per_sec;
	private double total_hits;
	private double tigereye_brew_uptime;
	private double tiger_strikes_uptime;
	private double fof_uptime;
	private double xuen_swings_per_sec;
	
	private static final double reaction_time = 0.5;
	private static final double PRECISION = Math.pow(10, -8);
	
	public FastMonk(BaseStats bs){
		stats = new Stats();
		stats.setBuffs(new Buffs()); // raid buffs
		stats.setSpec(bs.getSpec());
		stats.add(bs);
		
		stats.setMh(new WeaponStats(bs.getMH().dps, bs.getMH().speed, bs.getMH().type));
		if(bs.getOH() != null && !bs.getMH().type.isTwoHander())
			stats.setOh(new WeaponStats(bs.getOH().dps, bs.getOH().speed, bs.getOH().type));
		else
			stats.setOh(new WeaponStats());
		
		
		stats.setGearBuffs(bs.getGearBuffs());
		stats.setGearBuffItemLevel(bs.getGearBuffItemLevel());
		if(!bs.getMH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("mh_"+bs.getMH().getEnchant());
		if(!stats.getMh().getType().isTwoHander() && !bs.getOH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("oh_"+bs.getOH().getEnchant());
		
//		for(String gbs : stats.getGearBuffs().getGearBuffs()){
//			System.out.println("gb " + gbs);
//		}

		stats.setSetItems(bs.getSetItems());
		stats.setTalents(bs.getTalents());
		stats.setGlyphs(bs.getGlyphs());
		
//		for(String sbs : stats.getGearBuffs().getSetItems()){
//			System.out.println("setitems " + sbs);
//		}
//		for(String t : bs.getTalents()){
//			System.out.println("talents " + t);
//		}
//		for(String gl : bs.getGlyphs()){
//			System.out.println("||glyph " + gl);
//		}
		
		calc();
	}
	
	public FastMonk(){
		stats = new Stats();
	}
	
	private void calc(){

		attacks = new AttacksMonk(this);
		dps1 = get_dps();
		dpsBreakdown = new HashMap<String,Double>(dps);
		for(String attack : dps.keySet()){
			System.out.println(attack + " " + dps.get(attack) + " (" + ((double)Math.round(dps.get(attack)/dps1*10000))/100 + "%)");
		}
		System.out.println("DPS: " + dps1);
		
		// calc ep
		calc_ep();
	}
	
	public double get_dps(){
		
		aps.clear();
		dps.clear();
		dpsOnlyCrit.clear();
		total_hits = 0.0;
		
		if(stats.getSpec() == Spec.Windwalker)
			calc_dps_windwalker();
		else if(stats.getSpec() == Spec.Brewmaster)
			calc_dps_brewmaster();
		else if(stats.getSpec() == Spec.Mistweaver)
			new Exception("No Healer specs implemented");
		
		for(String attack : aps.keySet()){
			calc_dps_contribution(attacks.get_dmg(attack),attacks.get_crit(attack));
		}
		double dps1 = 0.0;
		for(String attack : dps.keySet()){
			dps1 += dps.get(attack);
		}
		return Math.max(dps1, 1.0);
	}
	
	private void calc_dps_windwalker() {
		double jab_ec = 32 + 8 / this.stats.getMhStyleHitChance();
				
		while(true) {
			
			double energy_regen = get_energy_regen();
			double jab_per_sec = energy_regen / jab_ec;
			aps.put("jab", jab_per_sec);
			
			double combo_breaker_proc_chance = stats.getBuffed(Stat.mst) * 0.014;
			double free_tiger_palm_per_sec = jab_per_sec * combo_breaker_proc_chance;

			double chi_per_sec = jab_per_sec * 2;
			if(stats.hasTalent("power_strikes"))
				chi_per_sec += 1./ (20 + reaction_time);
			if(stats.hasTalent("chi_brew"))
				chi_per_sec += 4. / (90 + reaction_time);
			
			double tiger_palm_every = 20 - reaction_time;
			double normal_tiger_palm_prob = Math.max(0.0, 1 - free_tiger_palm_per_sec * tiger_palm_every);
			double normal_tiger_palm_per_sec = normal_tiger_palm_prob / tiger_palm_every;
			System.out.println("normal_tiger_palm_prob " + normal_tiger_palm_prob);
			aps.put("tiger_palm", (free_tiger_palm_per_sec + normal_tiger_palm_per_sec) * stats.getMhStyleHitChance());
			
			double chi_left_per_sec = chi_per_sec - 1 * normal_tiger_palm_per_sec;
			System.out.println("chi_left_per_sec " + chi_left_per_sec);
			
			// fists of fury
			//double avg_built_time_of_1chi = chi_left_per_sec / 2;
			double fof_delay = 125.;
			double fof_cd = 25. + reaction_time + fof_delay;
			if(stats.getGearBuffs().hasSet("t14_2pc"))
				fof_cd -= 5.0;
			fof_uptime = 4. / fof_cd;
			double fof_per_sec = 1. / fof_cd;
			double fof_attacks_per_sec = 5. / fof_cd;
			aps.put("fists_of_fury", fof_attacks_per_sec);
			System.out.println("fof_uptime " + fof_uptime);
			System.out.println("fof_per_sec " + fof_per_sec);
			
			chi_left_per_sec -= 3 * fof_per_sec;
			System.out.println("chi_left_per_sec " + chi_left_per_sec);
			
			double kick_per_sec = chi_left_per_sec / 2;
			double rsk_per_sec = 1./8;
			double bk_per_sec = kick_per_sec - rsk_per_sec;
			
			aps.put("rising_sun_kick", rsk_per_sec * stats.getMhStyleHitChance());
			double free_blackout_kick_per_sec = jab_per_sec * combo_breaker_proc_chance;
			aps.put("blackout_kick", (bk_per_sec + free_blackout_kick_per_sec) * stats.getMhStyleHitChance());
			aps.put("blackout_kick_dot", aps.get("blackout_kick"));
			
			if(stats.hasTalent("invoke_xuen_the_white_tiger")){
				double xuen_cd = 180.;
				double xuen_dur = 45.;
				double xuen_uptime = xuen_dur / xuen_cd;
				aps.put("xuen_dot", xuen_uptime  * stats.getMhStyleHitChance());
				xuen_swings_per_sec = 1./2 * xuen_uptime;
				double mh_hit_chance = stats.getMhHitChance();
				if(stats.getMh().getType().isTwoHander())
					mh_hit_chance = stats.get2HandHitChance();
				aps.put("xuen_melee", xuen_swings_per_sec * mh_hit_chance);
			}
			
			this.white_attacks();
			
			double total_mh_hits = aps.get("mh") + aps.get("tiger_strike_mh");
			total_mh_hits += aps.get("jab") + aps.get("rising_sun_kick") + aps.get("tiger_palm") + aps.get("blackout_kick");
			double total_oh_hits = 0.0;
			if(!stats.getMh().getType().isTwoHander())
				total_oh_hits += aps.get("oh") + aps.get("tiger_strike_oh");
			
			double tigereye_brew_stacktime = 4 / chi_per_sec * 10;
			double tigereye_duration = 15.;
			tigereye_brew_uptime = tigereye_duration / (tigereye_brew_stacktime+tigereye_duration);
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;
			
			total_hits = aps.get("mh") + aps.get("tiger_strike_mh");
			total_hits += aps.get("jab");
			total_hits += aps.get("rising_sun_kick") + aps.get("tiger_palm") + aps.get("blackout_kick");
			if(!stats.getMh().getType().isTwoHander())
				total_hits += aps.get("oh") + aps.get("tiger_strike_oh");
			
			this.procs(total_mh_hits,total_oh_hits,0.0);	
		}
	}
	
	private void calc_dps_brewmaster() {
		
	}
	
	private void white_attacks(){
		double mh_hit_chance = this.stats.getMhHitChance();
		if(stats.getMh().getType().isTwoHander())
			mh_hit_chance = this.stats.get2HandHitChance();
		mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
		System.out.println("mh_swings_per_sec" + mh_swings_per_sec);
		oh_swings_per_sec = 0.0;
		if(!stats.getMh().getType().isTwoHander())
			oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
		mh_swings_per_sec *= (1-fof_uptime);
		oh_swings_per_sec *= (1-fof_uptime);
		double mh_hits_per_sec = mh_hit_chance * mh_swings_per_sec;
		double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;
		aps.put("mh", mh_hits_per_sec);
		if(!stats.getMh().getType().isTwoHander())
			aps.put("oh", oh_hits_per_sec);
		
		double proc_chance = 0.08;
		double hits_per_sec = mh_hits_per_sec + oh_hits_per_sec;
		double procs_per_second = proc_chance * hits_per_sec;
		tiger_strikes_uptime = 1 - Math.pow(1 - procs_per_second, 4 / hits_per_sec);
		aps.put("tiger_strike_mh", tiger_strikes_uptime * mh_hits_per_sec * this.stats.getMhStyleHitChance());
		if(!stats.getMh().getType().isTwoHander())
			aps.put("tiger_strike_oh", tiger_strikes_uptime * oh_hits_per_sec * this.stats.getOhStyleHitChance());
	}
	
	private double get_energy_regen(){
		double base_regen = 8;
		if(stats.getSpec() == Spec.Windwalker && stats.hasTalent("ascension"))
			base_regen = 8 * 1.15;
		double regen = base_regen * (1.0 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
		
		if(stats.getSpec() == Spec.Windwalker){
			double extra_energy = 60.0;
			if(stats.getGearBuffs().hasSet("t14_4pc"))
				extra_energy += 50;
			regen += extra_energy / 60.; // 60 energy every 60 sec;
		}
		if(stats.getGearBuffs().has("berserking"))
			regen += base_regen * 0.2 * 10 / 180.;
		if(stats.getGearBuffs().has("arcane_torrent"))
			regen += 15 * 1. / 120;
		System.out.println("regen " + regen);
		return regen;
	}
	
	protected double get_total_speed(){
		double base = 1.0;
		if(stats.getMh().getType().isTwoHander())
			base = 1.4;
		double speed = base * (1 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
		if(stats.getBuffs().hasBuff("attackspeed_buff"))
			speed *= 1.1;
		if(stats.getGearBuffs().has("berserking"))
			speed *= 1 + .2 * 10. / 180;
		if(stats.getGearBuffs().has("time_is_money"))
            speed *= 1.01;
		speed *= 1 + 0.5 * tiger_strikes_uptime;
		speed *= (1 + 0.3 * 40 / FIGHT_LENGTH);
		return speed;
	}
	
	private double get_eff_dmg(){
		return get_eff_dmg(Stats.BOSS_ARMOR);
	}
	//
	private double get_eff_dmg(double armor){
		double mod = 1;
		if(stats.getBuffs().hasBuff("armor_debuff"))
			mod -= 0.12;
		mod -= 0.3; // monk tiger palm skill 3 stacks
		double boss_armor = armor * mod;
		double param = 90 * 4037.5 - 317117.5; // LEVEL_90 player and target 93
		return param / (boss_armor + param);
	}

	private void calc_dps_contribution(AttacksBlock ab,double crit_chance){
		double hitDamage = ab.getHitDamage();
		double critDamage = ab.getCritDamage();
		
		double dmgMod = 1.0;
		if(ab.getType() == AttackType.physical){
			if(stats.getBuffs().hasBuff("physicaldmg_debuff"))
				dmgMod *= 1.04;
			dmgMod *= get_eff_dmg();
		}
		if(ab.getType() == AttackType.magical){
			if(stats.getBuffs().hasBuff("spelldmg_debuff"))
				dmgMod *= 1.05;
		}
		if(stats.getSpec() == Spec.Windwalker){
			dmgMod *= 1.2; // stance of the fierce tiger
			dmgMod *= 1 + 0.2 * tigereye_brew_uptime; // tigereye brew
			if(!Arrays.asList("mh","oh","tiger_strike_mh","tiger_strike_oh").contains(ab.getAttack())){
				dmgMod *= 1.1; // rising sun kick debuff
			}
		}
		
		hitDamage *= dmgMod;
		critDamage *= dmgMod;
		System.out.println(ab.getAttack() + " " + hitDamage);
		
		if(ab.getAttack().equals("mh") && !stats.getMh().getType().isTwoHander()){
			double hit = hitDamage * (stats.getMhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * mh_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * mh_swings_per_sec);
		}else if(ab.getAttack().equals("mh") && stats.getMh().getType().isTwoHander()){
			double hit = hitDamage * (stats.get2HandHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * mh_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * mh_swings_per_sec);
		}else if(ab.getAttack().equals("oh")){
			double hit = hitDamage * (stats.getOhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * oh_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * oh_swings_per_sec);
		}else if(ab.getAttack().equals("xuen_melee") && !stats.getMh().getType().isTwoHander()){
			double hit = hitDamage * (stats.getMhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * xuen_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * xuen_swings_per_sec);
		}else if(ab.getAttack().equals("xuen_melee") && stats.getMh().getType().isTwoHander()){
			double hit = hitDamage * (stats.get2HandHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * xuen_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * xuen_swings_per_sec);
		}else{
			double hit = hitDamage * ( 1 - crit_chance );
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit) * aps.get(ab.getAttack()));
			dpsOnlyCrit.put(ab.getAttack(), crit * aps.get(ab.getAttack()));
		}
	}

	@Override
	public Map<String, Double> getEP() {
		return ep;
	}
}
